package com.dandelion.framework.filter;


import cn.hutool.core.util.StrUtil;
import cn.hutool.json.JSONUtil;
import com.dandelion.business.entity.SysUser;
import com.dandelion.business.entity.vo.login.resp.LoginUserInfo;
import com.dandelion.business.entity.vo.login.resp.AopToken;
import com.dandelion.framework.common.R;
import com.dandelion.framework.common.RedisKey;
import com.dandelion.framework.common.util.UserUtils;
import com.dandelion.framework.common.util.WebUtils;
import com.dandelion.business.entity.vo.LoginUserDetail;
import com.dandelion.framework.common.util.JwtUtil;
import com.dandelion.framework.enums.CodeEnum;
import io.jsonwebtoken.Claims;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.stereotype.Component;
import org.springframework.web.filter.OncePerRequestFilter;

import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.concurrent.TimeUnit;

/**
 * jwt身份验证令牌过滤器
 *
 * @author 77088
 * @date 2021/12/25
 */

@Component
@Slf4j
public class JwtAuthenticationTokenFilter extends OncePerRequestFilter {


    /**
     * 访问令牌有效性,默认1小时
     */
    @Value("${dandelion.token.access-token-validity:3600000}")
    private Long accessTokenValidity;

    /**
     * 刷新令牌有效性,默认12小时
     */
    @Value("${dandelion.token.refresh-token-validity:43200000}")
    private Long refreshTokenValidity;


    @Autowired
    private StringRedisTemplate redisTemplate;


    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
        String accessTokenStr = request.getHeader("accessToken");
        String refreshTokenStr = request.getHeader("refreshToken");
        if (StrUtil.isBlank(accessTokenStr)) {
            //没有携带token直接放行
            filterChain.doFilter(request, response);
            return;
        }

        boolean needRefreshToken = false;
        //解析token
        String userId;
        try {
            Claims claims = JwtUtil.parseJWT(accessTokenStr);
            String subject = claims.getSubject();
            LoginUserInfo info = JSONUtil.toBean(subject, LoginUserInfo.class);
            userId = info.getUserId().toString();
        } catch (Exception e) {
            log.warn("accessToken过期尝试解析refreshToken,accessToken:{},refreshToken:{}", accessTokenStr, refreshTokenStr);
            try {
                //解析刷新token
                Claims claims = JwtUtil.parseJWT(refreshTokenStr);
                String subject = claims.getSubject();
                LoginUserInfo info = JSONUtil.toBean(subject, LoginUserInfo.class);
                userId = info.getUserId().toString();
                String userRefreshTokenKey = RedisKey.LoginModule.USER_REFRESH_TOKEN_PREFIX + userId;
                String userRefreshTokenVal = redisTemplate.opsForValue().get(userRefreshTokenKey);
                if (StrUtil.isBlank(userRefreshTokenVal) || !refreshTokenStr.equals(userRefreshTokenVal)) {
                    log.warn("refreshToken被多次使用,已阻止! refreshToken:{}", refreshTokenStr);
                    R r = R.fail(CodeEnum.UNAUTHORIZED.getCode(), CodeEnum.UNAUTHORIZED.getMsg());
                    WebUtils.renderString(response, JSONUtil.toJsonStr(r));
                    return;
                }
                needRefreshToken = true;
                //清空刷新refreshToken
                redisTemplate.delete(userRefreshTokenKey);
            } catch (Exception exception) {
                log.warn("refreshToken过期,重新登录!refreshToken:{}", refreshTokenStr);
                R r = R.fail(CodeEnum.UNAUTHORIZED.getCode(), CodeEnum.UNAUTHORIZED.getMsg());
                WebUtils.renderString(response, JSONUtil.toJsonStr(r));
                return;
            }
        }
        //从redis中获取用户信息
        String loginUserInfoKey = RedisKey.LoginModule.LOGIN_USER_INFO_PREFIX + userId;

        String accessTokenVal = redisTemplate.opsForValue().get(loginUserInfoKey);
        if (StrUtil.isBlank(accessTokenVal)) {
            log.warn("refreshToken过期,重新登录!refreshToken:{}", refreshTokenStr);
            R r = R.fail(CodeEnum.UNAUTHORIZED.getCode(), CodeEnum.UNAUTHORIZED.getMsg());
            WebUtils.renderString(response, JSONUtil.toJsonStr(r));
            return;
        }
        LoginUserDetail loginUserDetail = JSONUtil.toBean(accessTokenVal, LoginUserDetail.class);
        SysUser user = loginUserDetail.getUser();
        if (needRefreshToken) {
            LoginUserInfo baseInfo = new LoginUserInfo();
            baseInfo.setUserId(user.getId());
            baseInfo.setUserName(user.getUserName());
            baseInfo.setStatus(user.getState());
            baseInfo.setAvatar(user.getAvatar());

            //生成token
            String accessToken = JwtUtil.createJWT(JSONUtil.toJsonStr(baseInfo), accessTokenValidity);

            //生成刷新token
            String refreshToken = JwtUtil.createJWT(JSONUtil.toJsonStr(baseInfo), refreshTokenValidity);

            String userRefreshTokenKey = RedisKey.LoginModule.USER_REFRESH_TOKEN_PREFIX + userId;
            redisTemplate.opsForValue().set(userRefreshTokenKey, refreshToken, refreshTokenValidity + 300000, TimeUnit.MILLISECONDS);

            //redis的key
            redisTemplate.opsForValue().set(loginUserInfoKey, JSONUtil.toJsonStr(loginUserDetail), refreshTokenValidity + 300000, TimeUnit.MILLISECONDS);
            UserUtils.setToken(new AopToken(accessToken, refreshToken));
        }

        //存入SecurityContextHolder
        //TODO 获取权限信息封装到Authentication中
        UsernamePasswordAuthenticationToken authenticationToken =
                new UsernamePasswordAuthenticationToken(loginUserDetail, null, loginUserDetail.getAuthorities());
        SecurityContextHolder.getContext().setAuthentication(authenticationToken);

        //放行
        filterChain.doFilter(request, response);
    }
}
